iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0

今天來玩玩看 Python 中的有理數,先來看一下維基中有理數的定義:

可以表達為兩個整數比的數被定義為有理數。

因此,7/3是有理數,{\sqrt {2}}/則是無理數。

Python 有個內建 module 可以讓我們玩一下有理數:

from fractions import Fraction

看一下 Fraction class 的介紹:

help(Fraction)
Help on class Fraction in module fractions:

class Fraction(numbers.Rational)
 |  Fraction(numerator=0, denominator=None, *, _normalize=True)
 |  
 |  This class implements rational numbers.
 |  
 |  In the two-argument form of the constructor, Fraction(8, 6) will
 |  produce a rational number equivalent to 4/3. Both arguments must
 |  be Rational. The numerator defaults to 0 and the denominator
 |  defaults to 1 so that Fraction(3) == 3 and Fraction() == 0.
 |  
 |  Fractions can also be constructed from:
 |  
 |    - numeric strings similar to those accepted by the
 |      float constructor (for example, '-2.3' or '1e10')
 |  
 |    - strings of the form '123/456'
 |  
 |    - float and Decimal instances
 |  
 |    - other Rational instances (including integers)
 |  

看完說明,我們知道有很多種方法可以建出有理數,比如餵整數進去:

Fraction(1)    
Fraction(1, 1)
Fraction(1, 3)   # 第一個引數是分子,預設1,第二引述是分母,預設None
Fraction(1, 3)

我們也可以用 Fraction 建出 Fraction:

x = Fraction(2, 3)
y = Fraction(3, 4)
# 2/3 / 3/4 --> 2/3 * 4/3 --> 8/9
Fraction(x, y)
Fraction(8, 9)

也可以用浮點數建出物件:

Fraction(0.125)
Fraction(1, 8)
Fraction(0.5)
Fraction(1, 2)

甚至也可以傳字串進去:

Fraction('10.5')
Fraction(21, 2)
Fraction('22/7')
Fraction(22, 7)

Fractions 會自動約分:

Fraction(8, 16)
Fraction(1, 2)

如果是負數的話,負號會自動被移到分子(並不影響結果):

Fraction(1, -4)
Fraction(-1, 4)

Fraction 也支援四則運算:

Fraction(1, 3) + Fraction(1, 3) + Fraction(1, 3)
Fraction(1, 1)
Fraction(1, 2) * Fraction(1, 4)
Fraction(1, 8)
Fraction(1, 2) / Fraction(1, 3)
Fraction(3, 2)

我們可以從 Fraction 中取出分子和分母:

x = Fraction(22, 7)
print(x.numerator)
print(x.denominator)
22
7

浮點數的最大逼近

國中學過,圓周率 π 是 3.1415926...... 的無限循環小數,在電腦程式中,沒有無窮的記憶體存這種數字,所以即使是 π 這種數字,也是有限的小數。

既然是有限小數,就一定可以轉成分子是整數、分母也是整數的型態,也就是有理數:

import math
x = Fraction(math.pi)
print(x)
print(float(x))
884279719003555/281474976710656
3.141592653589793
x = Fraction(math.sqrt(2))  # 根號2在現實世界中是無理數,程式語言中則是取逼近值
print(x)
6369051672525773/4503599627370496

Note that these rational values are approximations to the irrational numbers $\pi$ and $\sqrt{2}$

Beware!!

Float number representations (as we will examine in future lessons) do not always have an exact representation.

但這邊要小心了!浮點數的逼近值有可能會變化!

0.125 (1/8) 是有完美正確的「逼近值」,所以永遠會轉化成分子分母相同的有理數:

Fraction(0.125)
Fraction(1, 8)

但 0.3 (3/10) 的逼近值並不「完美」,所以會看到怪異的事發生:

Fraction(3, 10)    
Fraction(3, 10)

不等於

Fraction(0.3)
Fraction(5404319552844595, 18014398509481984)

這塊未來會有更詳細的說明,這邊我們快速解釋一下:

x = 0.3
print(x)
0.3

浮點數 0.3 看起來完美無缺,因為 Python 為了閱讀方便會自動捨去位數。

我們可以故意強迫Python多顯示幾位數看看:

format(x, '.5f')
'0.30000'

也是很完美。

再多顯示一點好了⋯⋯

format(x, '.25f')
'0.2999999999999999888977698'

怪事發生了!0.3 變成了 0.2999999999999999888977698

我們可以用另外一種方式檢查:

delta = Fraction(0.3) - Fraction(3, 10)

delta 應該要是零對吧?結果不是:

delta == 0
False
delta
Fraction(-1, 90071992547409920)
float(delta)
-1.1102230246251566e-17

delta 是一個很小很小的浮點數。

限制分母

我們先對 pi 做 Fraction:

x = Fraction(math.pi)
print(x)
print(format(float(x), '.25f'))
884279719003555/281474976710656
3.1415926535897931159979635

你可以對 Fraction 限制分母,設限以後,Python 會以不超過該分母的值去產生(逼近)浮點數。

逼近的方法是 Fraction.limit_denominator:

y = x.limit_denominator(10)
print(y)
print(format(float(y), '.25f'))
22/7
3.1428571428571427937015414

上面分母不能超過10,逼近出來的 pi 值就跟 3.14159 差了一些。

y = x.limit_denominator(100)
print(y)
print(format(float(y), '.25f'))
311/99
3.1414141414141414365701621
y = x.limit_denominator(500)
print(y)
print(format(float(y), '.25f'))
355/113
3.1415929203539825209645642

可以看到分母限制放得越寬,pi的逼近也越準了。

好啦!今天有理數的學習就到這邊,明天開始就是一系列的浮點數練習嚕!
我們明~天~見~

參考:Python 3: Deep Dive (Part 1 - Functional)


上一篇
# Python 裡的整數——實作N進位演算法
下一篇
Python 與浮點數(float):一些小介紹
系列文
小青蛇變大蟒蛇——進階Python學起來!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言